/*
 * Copyright 2011-17 Fraunhofer ISE, energy & meteo Systems GmbH and other contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.openmuc.openiec61850;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.openmuc.openiec61850.internal.mms.asn1.AlternateAccess;
import org.openmuc.openiec61850.internal.mms.asn1.AlternateAccessSelection;
import org.openmuc.openiec61850.internal.mms.asn1.AlternateAccessSelection.SelectAccess;
import org.openmuc.openiec61850.internal.mms.asn1.AlternateAccessSelection.SelectAccess.Component;
import org.openmuc.openiec61850.internal.mms.asn1.BasicIdentifier;
import org.openmuc.openiec61850.internal.mms.asn1.Identifier;
import org.openmuc.openiec61850.internal.mms.asn1.ObjectName;
import org.openmuc.openiec61850.internal.mms.asn1.Unsigned32;
import org.openmuc.openiec61850.internal.mms.asn1.VariableDefs;
import org.openmuc.openiec61850.internal.mms.asn1.VariableSpecification;

public abstract class FcModelNode extends ModelNode {

    private VariableDefs.SEQUENCE variableDef = null;
    Fc fc;
    private ServerAssociation selected = null;
    private TimerTask task = null;

    public Fc getFc() {
        return fc;
    }

    @Override
    public String toString() {
        return getReference().toString() + " [" + fc + "]";
    }

    boolean select(ServerAssociation association, Timer timer) {
        if (selected != null) {
            if (selected != association) {
                return false;
            }
        }
        else {
            selected = association;
            association.selects.add(this);
        }

        ModelNode sboTimeoutNode = association.serverModel.findModelNode(objectReference, Fc.CF).getChild("sboTimeout");

        if (sboTimeoutNode == null) {
            return true;
        }

        long sboTimeout = ((BdaInt32U) sboTimeoutNode).getValue();

        if (sboTimeout == 0) {
            return true;
        }

        class SelectResetTask extends TimerTask {
            ServerAssociation association;

            SelectResetTask(ServerAssociation association) {
                this.association = association;
            }

            @Override
            public void run() {
                synchronized (association.serverModel) {
                    if (task == this) {
                        task = null;
                        deselectAndRemove(association);
                    }
                }
            }
        }

        if (task != null) {
            task.cancel();
        }

        task = new SelectResetTask(association);
        timer.schedule(task, sboTimeout);

        return true;

    }

    void deselectAndRemove(ServerAssociation association) {
        selected = null;
        if (task != null) {
            task.cancel();
            task = null;
        }
        association.selects.remove(this);
    }

    void deselect() {
        selected = null;
        if (task != null) {
            task.cancel();
            task = null;
        }
    }

    boolean isSelected() {
        return selected != null;
    }

    boolean isSelectedBy(ServerAssociation association) {
        return selected == association;
    }

    VariableDefs.SEQUENCE getMmsVariableDef() {

        if (variableDef != null) {
            return variableDef;
        }

        AlternateAccess alternateAccess = null;

        StringBuilder preArrayIndexItemId = new StringBuilder(objectReference.get(1));
        preArrayIndexItemId.append("$");
        preArrayIndexItemId.append(fc);

        int arrayIndexPosition = objectReference.getArrayIndexPosition();
        if (arrayIndexPosition != -1) {

            for (int i = 2; i < arrayIndexPosition; i++) {
                preArrayIndexItemId.append("$");
                preArrayIndexItemId.append(objectReference.get(i));
            }

            alternateAccess = new AlternateAccess();
            List<AlternateAccess.CHOICE> subSeqOfAlternateAccess = alternateAccess.getCHOICE();
            Unsigned32 indexBerInteger = new Unsigned32(Integer.parseInt(objectReference.get(arrayIndexPosition)));

            if (arrayIndexPosition < (objectReference.size() - 1)) {
                // this reference points to a sub-node of an array element

                StringBuilder postArrayIndexItemId = new StringBuilder(objectReference.get(arrayIndexPosition + 1));

                for (int i = (arrayIndexPosition + 2); i < objectReference.size(); i++) {
                    postArrayIndexItemId.append("$");
                    postArrayIndexItemId.append(objectReference.get(i));
                }

                BasicIdentifier subIndexReference = new BasicIdentifier(postArrayIndexItemId.toString().getBytes());

                AlternateAccessSelection.SelectAccess subIndexReferenceSelectAccess = new AlternateAccessSelection.SelectAccess();
                Component component = new Component();
                component.setBasic(subIndexReference);
                subIndexReferenceSelectAccess.setComponent(component);

                AlternateAccessSelection subIndexReferenceAlternateAccessSelection = new AlternateAccessSelection();
                subIndexReferenceAlternateAccessSelection.setSelectAccess(subIndexReferenceSelectAccess);

                AlternateAccess.CHOICE subIndexReferenceAlternateAccessSubChoice = new AlternateAccess.CHOICE();
                subIndexReferenceAlternateAccessSubChoice.setUnnamed(subIndexReferenceAlternateAccessSelection);

                AlternateAccess subIndexReferenceAlternateAccess = new AlternateAccess();

                List<AlternateAccess.CHOICE> subIndexReferenceAlternateAccessSubSeqOf = subIndexReferenceAlternateAccess
                        .getCHOICE();
                subIndexReferenceAlternateAccessSubSeqOf.add(subIndexReferenceAlternateAccessSubChoice);

                // set array index:

                AlternateAccessSelection.SelectAlternateAccess.AccessSelection indexAccessSelectionChoice = new AlternateAccessSelection.SelectAlternateAccess.AccessSelection();
                indexAccessSelectionChoice.setIndex(indexBerInteger);

                AlternateAccessSelection.SelectAlternateAccess indexAndLowerReferenceSelectAlternateAccess = new AlternateAccessSelection.SelectAlternateAccess();
                indexAndLowerReferenceSelectAlternateAccess.setAccessSelection(indexAccessSelectionChoice);
                indexAndLowerReferenceSelectAlternateAccess.setAlternateAccess(subIndexReferenceAlternateAccess);

                AlternateAccessSelection indexAndLowerReferenceAlternateAccessSelection = new AlternateAccessSelection();
                indexAndLowerReferenceAlternateAccessSelection
                        .setSelectAlternateAccess(indexAndLowerReferenceSelectAlternateAccess);

                AlternateAccess.CHOICE indexAndLowerReferenceAlternateAccessChoice = new AlternateAccess.CHOICE();
                indexAndLowerReferenceAlternateAccessChoice.setUnnamed(indexAndLowerReferenceAlternateAccessSelection);

                subSeqOfAlternateAccess.add(indexAndLowerReferenceAlternateAccessChoice);

            }
            else {
                SelectAccess selectAccess = new SelectAccess();
                selectAccess.setIndex(indexBerInteger);

                AlternateAccessSelection alternateAccessSelection = new AlternateAccessSelection();
                alternateAccessSelection.setSelectAccess(selectAccess);

                AlternateAccess.CHOICE alternateAccessChoice = new AlternateAccess.CHOICE();
                alternateAccessChoice.setUnnamed(alternateAccessSelection);

                subSeqOfAlternateAccess.add(alternateAccessChoice);
            }

        }
        else {

            for (int i = 2; i < objectReference.size(); i++) {
                preArrayIndexItemId.append("$");
                preArrayIndexItemId.append(objectReference.get(i));
            }
        }

        ObjectName.DomainSpecific domainSpecificObjectName = new ObjectName.DomainSpecific();
        domainSpecificObjectName.setDomainID(new Identifier(objectReference.get(0).getBytes()));
        domainSpecificObjectName.setItemID(new Identifier(preArrayIndexItemId.toString().getBytes()));

        ObjectName objectName = new ObjectName();
        objectName.setDomainSpecific(domainSpecificObjectName);

        VariableSpecification varSpec = new VariableSpecification();
        varSpec.setName(objectName);

        variableDef = new VariableDefs.SEQUENCE();
        variableDef.setAlternateAccess(alternateAccess);
        variableDef.setVariableSpecification(varSpec);

        return variableDef;
    }

}
